Ruby 日記 26日目: Enumeratorオブジェクトを返す
次のプログラムを実行するために__(1)__に適切なメソッドをすべて選んでください。
code:rb
class IPAddr
include Enumerable
def initialize(ip_addr)
@ip_addr = ip_addr
end
def each
return __(1)__ unless block_given?
@ip_addr.split('.').each do |octet|
yield octet
end
end
end
addr = IPAddr.new("192.10.20.30")
enum = addr.each
p enum.next # 192と表示される
p enum.next # 10と表示される
p enum.next # 20と表示される
p enum.next # 30と表示される
選択肢:
to_enum
enum_with
enum
enum_for
解説:
Enumerableモジュールをincludeしている
繰り返しを行なうクラスのための Mix-in。このモジュールの メソッドは全て each を用いて定義されているので、インクルード するクラスには each が定義されていなければなりません。
なるほど、すべてのメソッドがeachを用いて定義されているのか。まさにEnumerableって感じだね
今回の問題のIPAddrクラスにはeachメソッドが定義されているね++
eachメソッドは、
引数にブロックが与えられた場合はselfを返す
そうでない場合はEnumeratorオブジェクトを返す
というの実装にするのが慣例っぽいね。
例えばArrayとHashのドキュメントを読んでみる。
each {|item| .... } -> self
each -> Enumerator
各要素に対してブロックを評価します。
ブロックが与えられなかった場合は、自身と each から生成した Enumerator オブジェクトを返します。
each {|key, value| ... } -> self
each_pair {|key, value| ... } -> self
each -> Enumerator
each_pair -> Enumerator
ハッシュのキーと値を引数としてブロックを評価します。
反復の際の評価順序はキーが追加された順です。 ブロック付きの場合 self を、 無しで呼ばれた場合 Enumerator を返します。
each_pair は each のエイリアスです。
さて今回の問題に戻ると、
code:rb
addr = IPAddr.new("192.10.20.30")
enum = addr.each
p enum.next # 192と表示される
p enum.next # 10と表示される
p enum.next # 20と表示される
p enum.next # 30と表示される
引数なしでeachメソッドを呼び出して、その戻り値に対してnextメソッドを呼んでいる。
このことからも、引数なしで呼び出したeachメソッドの戻り値がEnumeratorオブジェクトであることを期待されている、と読み取ることができる。
すなわち__(1)__には、IPAddrのEnumeratorオブジェクトを返すためのメソッドを記載すれば良い、ということになる
Enumerator を生成するには Enumerator.newあるいは Object#to_enum, Object#enum_for を利用します。
なるほど、上記の3つを使えばいいんだね
また、一部の イテレータはブロックを渡さずに呼び出すと繰り返しを実行する代わりに enumerator を生成して返します。
これはまさにうえに記載したArrayやHashなどのことかな
Object#to_enumとObject#enum_forは同じメソッドか。
to_enum(method = :each, *args) -> Enumerator
enum_for(method = :each, *args) -> Enumerator
to_enum(method = :each, *args) {|*args| ... } -> Enumerator
enum_for(method = :each, *args) {|*args| ... } -> Enumerator
Enumerator.new(self, method, *args) を返します。
なるほど〜。「:eachメソッドを使ってselfのEnumeratorオブジェクトを作るよ〜」みたいな感じか
ブロックを指定した場合は Enumerator#size がブロックの評価結果を返 します。ブロックパラメータは引数 args です。
ふ〜ん(ここはよくわかっていない)
なので正解は「to_enum」と「enum_for」だね。
実行結果を見ておく
code:sh
# ruby gold/ex26/choice01.rb
"192"
"10"
"20"
"30"
# ruby gold/ex26/choice02.rb
gold/ex26/choice02.rb:9:in each': undefined local variable or method enum_with' for #<IPAddr:0x00000000a90a60 @ip_addr="192.10.20.30"> (NameError) from gold/ex26/choice02.rb:18:in `<main>'
# ruby gold/ex26/choice03.rb
gold/ex26/choice03.rb:9:in each': undefined local variable or method enum' for #<IPAddr:0x00000000a90a88 @ip_addr="192.10.20.30"> (NameError) from gold/ex26/choice03.rb:18:in `<main>'
# ruby gold/ex26/choice04.rb
"192"
"10"
"20"
"30"
/icons/hr.icon
code:gold/ex26/sample01.rb
class IPAddr
include Enumerable
def initialize(ip_addr)
@ip_addr = ip_addr
end
def each
return Enumerator.new(self) unless block_given?
@ip_addr.split('.').each do |octet|
yield octet
end
end
end
addr = IPAddr.new("192.10.20.30")
enum = addr.each
p enum.next # 192と表示される
p enum.next # 10と表示される
p enum.next # 20と表示される
p enum.next # 30と表示される
code:sh
# ruby gold/ex26/sample01.rb
gold/ex26/sample.rb:9: warning: Enumerator.new without a block is deprecated; use Object#to_enum
"192"
"10"
"20"
"30"
想定通りの挙動になった〜、けど、warningが出ているね。
ブロック引数なしで Enumerator.new を使うのは非推奨なんだね。Object#to_enumを使っていこう〜。
/icons/hr.icon
てか今回の問題のIPAddrクラスって、eachやnextなどしか使っていないので、Enumerableをincludeする必要はいまのところのないよね。
code:gold/ex26/sample02.rb
class IPAddr
def initialize(ip_addr)
@ip_addr = ip_addr
end
def each
return enum_for unless block_given?
@ip_addr.split('.').each do |octet|
yield octet
end
end
end
addr = IPAddr.new("192.10.20.30")
enum = addr.each
p enum.next # 192と表示される
p enum.next # 10と表示される
p enum.next # 20と表示される
p enum.next # 30と表示される
code:sh
# ruby gold/ex26/sample02.rb
"192"
"10"
"20"
"30"
/icons/hr.icon
next -> object
「次」のオブジェクトを返します。
現在までの列挙状態に応じて「次」のオブジェクトを返し、列挙状態を1つ分進めます。 列挙が既に最後へ到達している場合は、 StopIteration 例外を発生します。このとき列挙状態は変化しません。 つまりもう一度 next を呼ぶと再び例外が発生します。
next メソッドによる外部列挙の状態は他のイテレータメソッドによる 内部列挙には影響を与えません。 ただし、 IO#each_line のようにおおもとの列挙メカニズムが副作用を 伴っている場合には影響があり得ます。